package hu.myai.runtimeneural;

import hu.myai.util.DatabaseUtil;
import hu.myai.util.SanfordNet;

import org.joone.engine.Layer;
import org.joone.engine.Monitor;
import org.joone.engine.NeuralNetEvent;
import org.joone.engine.NeuralNetListener;
import org.joone.engine.learning.TeachingSynapse;
import org.joone.io.MemoryInputSynapse;
import org.joone.log.ILogger;
import org.joone.log.LoggerFactory;
import org.joone.net.NeuralNet;
import org.joone.net.NeuralNetValidator;
import org.joone.net.NeuralValidationEvent;
import org.joone.net.NeuralValidationListener;
import org.joone.util.LearningSwitch;

public abstract class TrainNeuralNetwork implements NeuralNetListener, NeuralValidationListener {

	private static final ILogger log = LoggerFactory.getLogger(TrainNeuralNetwork.class);

	private long startms;

	private String symbol = "";

	public NeuralNet nnet;

	public double trainError = 1;

	// for database util reference
	private DatabaseUtil dbUtil = new DatabaseUtil();

	private SanfordNet netUtil = new SanfordNet();

	/** * Creates a new instance of SampleScript */
	public TrainNeuralNetwork(NeuralNet nnet) {
		this.nnet = nnet;
	}

	public void go(String symbol) {
		if (nnet != null) {

			this.symbol = symbol;

			// set the input of the NET
			Layer input = nnet.getInputLayer();
			input.removeAllInputs();

			// set the output of the NET
			Layer output = nnet.getOutputLayer();
			output.removeAllOutputs();

			int timeSeries = netUtil.getTimeSeries();

			double[][] historyDataArray = dbUtil.getHistoryForExecute(netUtil.geTZeroDate(), symbol);
			double[][] historyValidArray = dbUtil.getHistoryForExecute(netUtil.geTZeroDate(), symbol);

			if (historyDataArray == null || historyValidArray == null) {
				log.error("Error - no data in the database");
				return;
			}
			int numberOfData = historyDataArray.length;
			int numOfValRecords = historyValidArray.length;

			// log.info("Number of data line are "+ numberOfData+" \t "+
			// dataFile);
			// log.info("Number of val. rekords line are "+ numOfValRecords+" \t
			// "+ validFile);

			// skipp if less data
			if (numberOfData < timeSeries + 1 || numOfValRecords < timeSeries + 1) {
				log.info("no aim to make the training, less then " + timeSeries + " records");
				throw new org.joone.exception.JooneRuntimeException("Not enough input for training.");
			}

			/* Creates all the required input data sets */
			MemoryInputSynapse ITdata = netUtil.createInput(historyDataArray, 1, 2, 8); /*
																						 * The
																						 * input
																						 * training
																						 * data
																						 * set
																						 */
			MemoryInputSynapse DTdata = netUtil.createValidInput(historyDataArray, 3, 9, 11); /*
																							 * The
																							 * desired
																							 * training
																							 * data
																							 * set
																							 */

			MemoryInputSynapse IVdata = netUtil.createInput(historyValidArray, 1, 2, 8); /*
																						 * The
																						 * input
																						 * validation
																						 * data
																						 * set
																						 */
			MemoryInputSynapse DVdata = netUtil.createValidInput(historyValidArray, 3, 9, 11); /*
																								 * The
																								 * desired
																								 * validation
																								 * data
																								 * set
																								 */

			/* Creates and attach the input learning switch */
			LearningSwitch Ilsw = netUtil.createSwitch(ITdata, IVdata);

			// attach the new input
			input.addInputSynapse(Ilsw);

			/* Creates and attach the desired learning switch */
			LearningSwitch Dlsw = netUtil.createSwitch(DTdata, DVdata);
			TeachingSynapse ts = new TeachingSynapse(); // The teacher of the
			// net
			ts.setDesired(Dlsw);

			// attache the new output
			output.addOutputSynapse(ts);

			/* Now we put all togheter into a NeuralNet object */
			nnet.setTeacher(ts);

			// nnet.addNoise(0.0004);

			/* Sets the Monitor's parameters */
			Monitor mon = nnet.getMonitor();

			// set the monitor parameters
			mon.setLearningRate(0.005);
			mon.setMomentum(0.002);
			mon.setSupervisioned(true);
			mon.setUseRMSE(true);
			mon.setTrainingPatterns(numberOfData - 2);
			mon.setValidationPatterns(numOfValRecords - 2);
			mon.setTotCicles(30);
			mon.setPreLearning(timeSeries);
			mon.setLearning(true);
			mon.setValidation(false);

			// Registers itself as a listener
			nnet.getMonitor().addNeuralNetListener(this);
			nnet.start();
			startms = System.currentTimeMillis();
			nnet.getMonitor().Go();
		}
	}

	/* Events */
	public void netValidated(NeuralValidationEvent event) {
		// Shows the RMSE at the end of the cycle
		NeuralNet NN = (NeuralNet) event.getSource();
		double validError = NN.getMonitor().getGlobalError();
		log.info("    Validation Error: " + validError);

		/* if the error is less the a limit the net is going to be saved */

		if (validError < 0.0030 && trainError < 0.003) {
			netUtil.saveNeuralNet(NN, symbol + "_netValidated_" + (validError + trainError) / 2 + ".snet");
			log.info("    NeuralNet is saved as: " + symbol + "_netValidated_" + (validError + trainError) / 2
					+ ".snet");
		}
	}

	public void cicleTerminated(NeuralNetEvent e) {
		// Prints out the cycle and the training error
		int cycle = nnet.getMonitor().getTotCicles() - nnet.getMonitor().getCurrentCicle() + 1;
		if (cycle % 100 == 0) { // We validate the net every 200 cycles

			trainError = nnet.getMonitor().getGlobalError();

			log.info("Cycle #" + cycle + "   " + (System.currentTimeMillis() - startms) + " ms");
			log.info("    Training Error:   " + trainError);

			// reset the timer
			startms = System.currentTimeMillis();

			// Creates a copy of the neural network
			nnet.getMonitor().setExporting(true);
			NeuralNet newNet = nnet.cloneNet();
			nnet.getMonitor().setExporting(false);

			// Cleans the old listeners
			// This is a fundamental action to avoid that the validating net
			// calls the cicleTerminated method of this class
			newNet.removeAllListeners();

			// Set all the parameters for the validation
			NeuralNetValidator nnv = new NeuralNetValidator(newNet);
			nnv.addValidationListener(this);
			nnv.start(); // Validates the net
			// nnv.getNeuralNet().join();
		}
	}

	public void errorChanged(NeuralNetEvent e) {
		// Monitor NN = (Monitor)e.getSource();
		// log.info(" Actual training error: "+NN.getGlobalError());
	}

	public void netStarted(NeuralNetEvent e) {
	}

	public void netStopped(NeuralNetEvent e) {
		log.info("Stopped after " + (System.currentTimeMillis() - startms) + " ms");
	}

	public void netStoppedError(NeuralNetEvent e, String error) {
	}

}
